﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Web.Services.Description;
using System.Xml.Serialization;
using System.Xml.Schema;
using System.CodeDom;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Dynamic;
using System.Reflection;
using System.Web.Services.Protocols;

namespace HttpClient
{
    public class WSDLManager
    {
        #region Private
        WebClient _client;
        string errorMessage = "";
        #endregion

        #region Propertys
        public string ErrorMessage
        {
            get { return errorMessage; }
            set { errorMessage = value; }
        }

        public ServiceDescription WSDLDesciription
        {
            get;
            set;
        }

        public SoapHttpClientProtocol Proxy
        {
            get;
            set;
        }
        #endregion
        #region Constructor

        public WSDLManager(WebClient client)
        {
            _client = client;
        }
        #endregion

        #region API
        public virtual object GetProxy(string wsdlUrl, string clientNamespace)
        {
            // CodeCompileUnit codeCompileUnit = GenerateWsdl(wsdlUrl);
            CodeNamespace ns = new CodeNamespace(clientNamespace);
            ServiceDescriptionImporter importer = GenerateImporter(wsdlUrl);
            importer.ProtocolName = "SOAP";
            importer.CodeGenerationOptions = CodeGenerationOptions.None;
            //importer.CodeGenerationOptions = CodeGenerationOptions.GenerateNewAsync | CodeGenerationOptions.GenerateOldAsync;
            CodeCompileUnit ccu = new CodeCompileUnit();
            ccu.Namespaces.Add(ns);
            importer.Import(ns, ccu);
            Proxy = GetClientTypeProxy(ccu,ns) as SoapHttpClientProtocol;
            return Proxy;
        }
        #endregion
        #region Protected & Private
        protected ServiceDescriptionImporter GenerateImporter(string wsdlUrl)
        {
            ServiceDescription sd = ServiceDescription.Read(_client.OpenRead(wsdlUrl));
            ServiceDescriptionImporter importer = new ServiceDescriptionImporter();
            importer.AddServiceDescription(sd, null, null);
            foreach (XmlSchema wsdlSchema in sd.Types.Schemas)
            {
                foreach (XmlSchemaObject externalSchema in wsdlSchema.Includes)
                {
                    if (externalSchema is XmlSchemaImport)
                    {
                        Uri baseUri = new Uri(wsdlUrl);
                        Uri schemaUri = new Uri(baseUri, ((XmlSchemaExternal)externalSchema).SchemaLocation);

                        Stream schemaStream = _client.OpenRead(schemaUri);
                        System.Xml.Schema.XmlSchema schema = XmlSchema.Read(schemaStream, null);
                        importer.Schemas.Add(schema);
                    }
                }
            }
            WSDLDesciription = sd;
            return importer;
        }
        protected virtual object GetClientTypeProxy(CodeCompileUnit codeCompileUnit, CodeNamespace ns)
        {
            string wsProxyTypeName = FindProxyTypeAndAugmentCodeDom(ns);
            CodeDomProvider provider = CodeDomProvider.CreateProvider("CS");
            CompilerParameters compilerParams = new CompilerParameters();
            compilerParams.GenerateInMemory = true;
            compilerParams.IncludeDebugInformation = false;
            //compilerParams.ReferencedAssemblies.Add(typeof(Microsoft.Scripting.Hosting.ScriptRuntime).Assembly.Location); //DLR
            CompilerResults results = provider.CompileAssemblyFromDom(compilerParams, codeCompileUnit);

            if (results.Errors.Count > 0)
            {
                throw new Exception("Error while compiling" + results.Errors[0].ToString());
            }

            Assembly generatedAssembly = results.CompiledAssembly;

            // find the type derived from SoapHttpClientProtocol
            Type wsProxyType = generatedAssembly.GetType(wsProxyTypeName);

            if (wsProxyType == null)
            {
                wsProxyType = generatedAssembly.GetType(ns.Name + "." + wsProxyTypeName);
                if (wsProxyType == null)
                {
                    throw new InvalidOperationException("Web service proxy type not generated.");
                }
            }
            return Activator.CreateInstance(wsProxyType);
        }
        string FindProxyTypeAndAugmentCodeDom(CodeNamespace codeNamespace)
        {
            string nsName = string.Format("ws_namespace_{0:x}", Guid.NewGuid().GetHashCode());
            CodeTypeDeclaration nsType = new CodeTypeDeclaration(nsName);
            CodeTypeDeclaration wsType = null; // the web service type (only one)

            foreach (CodeTypeDeclaration t in codeNamespace.Types)
            {
                string name = t.Name;

                // find the one derived from SoapHttpClientProtocol
                foreach (CodeTypeReference baseType in t.BaseTypes)
                {
                    if (baseType.BaseType == typeof(SoapHttpClientProtocol).FullName)
                    {
                        if (wsType != null)
                        {
                            throw new InvalidDataException("Found more than one web service proxy type.");
                        }

                        wsType = t;
                    }
                }

                // add the corresponding property to the namespace type
                CodeMemberProperty p = new CodeMemberProperty();
                p.Attributes &= ~MemberAttributes.AccessMask;
                p.Attributes |= MemberAttributes.Public;
                p.Name = name; // same as type name
                p.Type = new CodeTypeReference(typeof(Type));
                p.GetStatements.Add(new CodeMethodReturnStatement(new CodeTypeOfExpression(name)));
                nsType.Members.Add(p);
            }

            if (wsType == null)
            {
                // must have exactly one ws proxy
                throw new InvalidDataException("Web service proxy type not found.");
            }

            codeNamespace.Types.Add(nsType);

            // add ServiceNamespace property of the above type to the proxy type
            CodeMemberField nsField = new CodeMemberField(nsName, "_serviceNamespace");
            nsField.Attributes &= ~MemberAttributes.AccessMask;
            nsField.Attributes |= MemberAttributes.Private;
            nsField.InitExpression = new CodeObjectCreateExpression(nsName);
            wsType.Members.Add(nsField);

            CodeMemberProperty nsProp = new CodeMemberProperty();
            nsProp.Attributes &= ~MemberAttributes.AccessMask;
            nsProp.Attributes |= MemberAttributes.Public;
            nsProp.Name = "ServiceNamespace";
            nsProp.Type = new CodeTypeReference(nsName);
            nsProp.GetStatements.Add(new CodeMethodReturnStatement(
                new CodePropertyReferenceExpression(
                    new CodeThisReferenceExpression(),
                    "_serviceNamespace")));
            wsType.Members.Add(nsProp);

            // return the proxy type name
            return wsType.Name;
        }
        #endregion

    }

}
